package com.wys.spring.filter;

import com.google.common.collect.Lists;
import com.wys.api.common.BaseErrorCode;
import com.wys.api.common.R;
import com.wys.api.exception.BizException;
import com.wys.spring.ApplicationContextHolder;
import com.wys.spring.RequestBodyHandler;
import com.wys.spring.SpringCommonProperties;
import com.wys.spring.WYSHttpServletRequestWrapper;
import com.wys.spring.log.RequestLog;
import com.wys.spring.controller.servlet.RequestPathMappingHandler;
import com.wys.utils.JsonUtils;
import com.wys.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;

import static org.springframework.http.MediaType.*;

@Slf4j
@Order(Integer.MIN_VALUE)
public class SpringMVCLoggingFilter extends OncePerRequestFilter {

    private final List<MediaType> LOG_BODY_MEDIA_TYPE = Lists.newArrayList(APPLICATION_JSON,
            APPLICATION_FORM_URLENCODED, APPLICATION_XML, TEXT_PLAIN, TEXT_XML);

    @Resource
    private SpringCommonProperties springCommonProperties;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
        long startTime = System.currentTimeMillis();
        RequestLog requestLog = null;
        HttpServletRequest servletRequest = null;
        try {
            requestLog = RequestLog.createRequestLog(request, null, null);
            if (needLogBody(request)) {
                WYSHttpServletRequestWrapper contentCachingRequestWrapper = new WYSHttpServletRequestWrapper(request);
                RequestBodyHandler.setRequestBody(contentCachingRequestWrapper.getStringBody());
                requestLog = RequestLog.createRequestLog(contentCachingRequestWrapper, contentCachingRequestWrapper.getStringBody(), null);
                servletRequest = contentCachingRequestWrapper;
            }
            if (ObjectUtils.isNotEmpty(springCommonProperties.getLogging()) && springCommonProperties.getLogging().getRequestEnable()) {
                log.warn("拦截请求URL:{}", JsonUtils.object2Json(requestLog));
            }
            RequestPathMappingHandler requestPathMappingHandler = ApplicationContextHolder.context.getBean(RequestPathMappingHandler.class);
            if (ObjectUtils.isNotEmpty(requestPathMappingHandler) && ObjectUtils.isNotEmpty(requestPathMappingHandler.getExcludePaths())) {
                for (String excludePath : requestPathMappingHandler.getExcludePaths()) {
                    if (request.getRequestURI().contains(excludePath)) {
                        super.logger.warn("检测到存在排除的RequestPath：" + JsonUtils.object2Json(excludePath));
                        throw new HttpRequestMethodNotSupportedException("不支持的请求路径");
                    }
                }
            }
            filterChain.doFilter(ObjectUtils.isEmpty(servletRequest) ? request : servletRequest, response);
        } catch (BizException b) {
            log.error("业务异常:", b);
            ServletUtils.renderString(response, JsonUtils.object2Json(R.fail(b.getStatus(), b.getMessage())));
        } catch (HttpRequestMethodNotSupportedException h) {
            ServletUtils.renderString(response, JsonUtils.object2Json(R.fail(BaseErrorCode.UNAUTHORIZED.getStatus(), BaseErrorCode.UNAUTHORIZED.getMessage())));
            log.error("请求的路径：{}已被排除在当前服务中！！！", request.getRequestURI());
        } catch (Exception e) {
            log.error("springMVC 过滤器异常:", e);
            ServletUtils.renderString(response, JsonUtils.object2Json(R.fail(BaseErrorCode.REQUEST_SERVICE_ERROR.getStatus(), BaseErrorCode.REQUEST_SERVICE_ERROR.getMessage())));
        } finally {
            if (ObjectUtils.isNotEmpty(springCommonProperties.getLogging()) && springCommonProperties.getLogging().getResponseEnable()) {
                log.warn("拦截响应URL:{}  请求耗时:{}ms", requestLog.getUrl(), System.currentTimeMillis() - startTime);
            }
            RequestBodyHandler.removeAll();
        }
    }

    /**
     * 如果不是get/head/options请求, 并且消息头不是和文件或二进制有关的, 就记录消息体.
     *
     * @param request
     * @return
     */
    private boolean needLogBody(HttpServletRequest request) {

        boolean isFirstRequest = !isAsyncDispatch(request);

        if (!isFirstRequest || (request instanceof ContentCachingRequestWrapper)) return false;

        if (!"PUT".equalsIgnoreCase(request.getMethod()) && !"POST".equalsIgnoreCase(request.getMethod())
                && !"DELETE".equalsIgnoreCase(request.getMethod())&&"options".equalsIgnoreCase(request.getMethod())) return false;

        try {
            String contentType = request.getContentType();
            if (StringUtils.isNotBlank(contentType) && !"null".equalsIgnoreCase(contentType.trim())) {
                MediaType mediaType = MediaType.parseMediaType(contentType);
                if (LOG_BODY_MEDIA_TYPE.stream().noneMatch(mediaType::includes)) {
                    return false;
                }
            }
        } catch (Exception e) {
            logger.warn("", e);
        }

        return true;
    }

}
